Repository Public
1// Vrai-Prix — méthode du coût : fiche d'un assemblage.2import type { Metadata } from "next";3import { notFound } from "next/navigation";4import AssemblyDetail, { type AssemblyDetailData } from "@/components/cout/AssemblyDetail";5import { buildContext, loadLocations } from "@/lib/cost/catalog";6import { assemblyUnitCost } from "@/lib/cost/engine";7import { cached } from "@/lib/cost/cache";89export const dynamic = "force-dynamic";1011function load(code: string): AssemblyDetailData | null {12 return cached(`assembly-page:${code}`, 10 * 60_000, () => {13 const ctx = buildContext({ locationCode: "QC-MTL" });14 const a = ctx.assemblies.get(code);15 if (!a) return null;16 const unit = assemblyUnitCost(a, ctx);17 const regional = loadLocations().map((l) => ({ code: l.code, nameFr: l.nameFr, nameEn: l.nameEn, direct: Math.round(100 * (unit.material * l.materialFactor + unit.labour * l.labourFactor + unit.equipment * l.equipmentFactor)) / 100, materialFactor: l.materialFactor, labourFactor: l.labourFactor, equipmentFactor: l.equipmentFactor }));18 const history: AssemblyDetailData["history"] = [];19 const now = new Date(ctx.asOf);20 for (let k = 7; k >= 0; k--) {21 const d = new Date(now); d.setMonth(d.getMonth() - 3 * k);22 const asOf = d.toISOString().slice(0, 10);23 const c2 = k === 0 ? ctx : buildContext({ asOf, locationCode: "QC-MTL" });24 const u2 = assemblyUnitCost(a, c2);25 const kinds = new Set(u2.components.filter((c) => c.itemCode).map((c) => c.provenance.kind));26 history.push({ date: asOf, direct: u2.direct, material: u2.material, labour: u2.labour, equipment: u2.equipment, kind: kinds.has("observed") || kinds.has("official") ? "observed" : kinds.has("indexed") ? "indexed" : "reference" });27 }28 const neighbours = [...ctx.assemblies.values()].filter((x) => x.category === a.category && x.code !== a.code).slice(0, 8).map((x) => { const u = assemblyUnitCost(x, ctx); return { code: x.code, nameFr: x.nameFr, nameEn: x.nameEn, unit: x.unit, direct: u.direct }; });29 return { asOf: ctx.asOf, location: ctx.location, assembly: a, unit, regional, history, neighbours };30 });31}3233export async function generateMetadata({ params }: { params: Promise<{ code: string }> }): Promise<Metadata> {34 const { code } = await params;35 const d = load(decodeURIComponent(code));36 if (!d) return { title: "Assemblage introuvable" };37 return { title: `${d.assembly.nameFr} — ${d.unit.direct.toLocaleString("fr-CA", { maximumFractionDigits: 2 })} $ / ${d.assembly.unit} · assemblage`, description: d.assembly.descriptionFr, alternates: { canonical: `/cout/assemblage/${encodeURIComponent(d.assembly.code)}` } };38}3940export default async function AssemblyPage({ params }: { params: Promise<{ code: string }> }) {41 const { code } = await params;42 const d = load(decodeURIComponent(code));43 if (!d) notFound();44 return <AssemblyDetail data={d} />;45}46